package service

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"sync"
	"time"

	"github.com/tokentransfer/node/account"
	"github.com/tokentransfer/node/crypto"
	"github.com/tokentransfer/node/util"

	libaccount "github.com/tokentransfer/interfaces/account"
	libcore "github.com/tokentransfer/interfaces/core"
	libcrypto "github.com/tokentransfer/interfaces/crypto"
)

var as = account.GetAccountService()
var cs = crypto.GetCryptoService()
var ZERO = libcrypto.ZeroHash(cs)

type Applet struct {
	key     libaccount.Key
	genesis *Message

	list   []*Message
	m      map[string]uint64
	locker *sync.RWMutex
}

func CreateApplet() (*Applet, error) {
	data := make([]byte, 64)
	_, err := rand.Reader.Read(data)
	if err != nil {
		return nil, err
	}
	_, key, err := as.GenerateFamilySeed(hex.EncodeToString(data))
	if err != nil {
		return nil, err
	}
	genesis := &Message{Last: ZERO, Index: 0, Input: &Item{}, Output: &Item{}, Time: time.Now().UnixMilli()}
	err = cs.Sign(key, genesis.Input)
	if err != nil {
		return nil, err
	}
	err = cs.Sign(key, genesis.Output)
	if err != nil {
		return nil, err
	}
	_, err = cs.RawHash(genesis)
	if err != nil {
		return nil, err
	}
	return &Applet{key: key, genesis: genesis, list: []*Message{
		genesis,
	}, m: map[string]uint64{
		genesis.GetHash().String(): 0,
	}, locker: &sync.RWMutex{}}, nil
}

func (at *Applet) GetHash() libcore.Hash {
	return at.genesis.GetHash()
}

func (at *Applet) AddMessage(inputItem *Item, outputData []byte) (*Item, error) {
	outputItem := &Item{Data: outputData}
	err := cs.Sign(at.key, outputItem)
	if err != nil {
		return nil, err
	}
	lm, err := at.GetMessageByIndex(at.GetNumber())
	if err != nil {
		return nil, err
	}

	_, err = cs.RawHash(inputItem)
	if err != nil {
		return nil, err
	}
	msg := &Message{Last: lm.GetHash(), Index: lm.Index + 1, Input: inputItem, Output: outputItem, Time: time.Now().UnixMilli()}
	_, err = cs.RawHash(msg)
	if err != nil {
		return nil, err
	}

	at.locker.Lock()
	defer at.locker.Unlock()
	at.list = append(at.list, msg)
	at.m[msg.GetHash().String()] = uint64(len(at.list) - 1)
	return outputItem, nil
}

func (at *Applet) GetNumber() uint64 {
	at.locker.RLock()
	defer at.locker.RUnlock()

	return at.list[len(at.list)-1].Index
}

func (at *Applet) GetMessageByIndex(index uint64) (*Message, error) {
	at.locker.RLock()
	defer at.locker.RUnlock()

	ll := uint64(len(at.list))
	if index >= ll {
		return nil, util.ErrorOfNotFound(fmt.Sprintf("index: %d", index), "message")
	}
	msg := at.list[index]
	if msg.Index != index {
		return nil, util.ErrorOfUnmatched("index", "item", index, msg.Index)
	}
	return msg, nil
}

func (at *Applet) GetMessageByHash(hash string) (*Message, error) {
	at.locker.RLock()
	defer at.locker.RUnlock()

	index, ok := at.m[hash]
	if !ok {
		return nil, util.ErrorOfNotFound(hash, "message")
	}
	ll := uint64(len(at.list))
	if index >= ll {
		return nil, util.ErrorOfNotFound(fmt.Sprintf("index: %d", index), "message")
	}
	msg := at.list[index]
	if msg.Index != index {
		return nil, util.ErrorOfUnmatched("index", "item", index, msg.Index)
	}
	return msg, nil
}
